﻿//$LastChangedDate: 2012-02-17 09:49:19 +0100 (vr, 17 feb 2012) $
//$LastChangedRevision: 13 $
//$LastChangedBy: jan-willem $

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using TLD_Vision;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace TLD_Vision
{
    public class Image
    {
        private String originalPath;
        private Bitmap bmp;
        private BitmapData bmpData;

        public int Width { get { return bmp.Width; } }
        public int Height { get { return bmp.Height; } }


        public Image(String path) {
            bmp = new Bitmap(path);
            originalPath = System.IO.Path.GetFileName(path);
        }

        public Image(Bitmap bmp)
        {
            this.bmp = bmp;
        }

        public Image(int width, int height, PixelFormat format)
        {
            bmp = new Bitmap(width, height, format);
        }

        public Bitmap GetBitmap()
        {
            return bmp;
        }

        public IntPtr GetDataPointer()
        {
            return GetDataPointer(new Rectangle(Point.Empty, bmp.Size));
        }

        public IntPtr GetDataPointer(Rectangle rect)
        {
            bmpData = bmp.LockBits(rect, ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);
            return bmpData.Scan0;
        }

        public void Unlock() {
            bmp.UnlockBits(bmpData);
        }

        public Image Clone()
        {
            return new Image(new Bitmap(bmp));
        }

        public Image ManipulatePixels(TLD_Vision.filters.SinglePixelOperation processPixel)
        {
            Bitmap returnImage = new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppArgb);
            Rectangle rect = new Rectangle(Point.Empty, bmp.Size);

            BitmapData destBmpData = returnImage.LockBits(rect, ImageLockMode.WriteOnly, returnImage.PixelFormat);

            // Get the address of the first line.
            IntPtr srcPtr = GetDataPointer(rect);
            IntPtr dstPtr = destBmpData.Scan0;
            unsafe
            {
                uint* pBase = (uint*)srcPtr.ToPointer();
                uint* pDest = (uint*)dstPtr.ToPointer();
                for (int i = 0; i < (bmpData.Stride / 4) * bmpData.Height; i++)
                {
                    *(pDest++) = processPixel(*(pBase++));
                }
            }

            Unlock();
            returnImage.UnlockBits(destBmpData);

            return new Image(returnImage);
        }

        // TODO: Needs to be fixed for new ImageData setup.
        //public Image ApplyKernel(Kernel kernel)
        //{
        //    if (kernel.CanNormalize)
        //    {
        //        kernel.Normalize();
        //    }

        //    if (kernel.IsNormalized)
        //    {
        //        return ApplyMask(kernel.KernelData);
        //    }
        //    else
        //    {
        //        // TODO: Probably shouldn't return ImageData but a float array instead.
        //        ImageData data = ApplyUnnormalizedMask(kernel.KernelData);
        //        //data.RemapToARGB();
        //        return data.ToImage();
        //    }
        //}

        // TODO: Needs to be fixed for new ImageData setup.
        // Only works for grayscale.
        //public ImageData ApplyUnnormalizedMask(float[][] mask)
        //{
        //    int size = mask.GetLength(0);

        //    // Get the address of the first line.
        //    IntPtr srcPtr = GetDataPointer();

        //    int width = bmpData.Stride / 4;
        //    int radius = size >> 1;

        //    int yDone = bmpData.Height - radius;
        //    int xDone = bmpData.Width - radius;

        //    ImageData dataOut = new ImageData(width, bmp.Height);

        //    unsafe
        //    {
        //        uint* pBase = (uint*)srcPtr.ToPointer();
        //        int[] destArray = dataOut.Data;

        //        for (int y = radius; y < yDone; y++)
        //        {
        //            for (int x = radius; x < xDone; x++)
        //            {
        //                float sum = 0;
        //                for (int mY = -radius; mY <= radius; mY++)
        //                {
        //                    for (int xY = -radius; xY <= radius; xY++)
        //                    {
        //                        byte* bgra = (byte*)&pBase[(y + mY) * width + x + xY];
        //                        float maskValue = mask[mY + radius][xY + radius];
        //                        sum += bgra[0] * maskValue;
        //                    }
        //                }

        //                destArray[y * width + x] = (int)sum;
        //            }
        //        }
        //    }

        //    Unlock();

        //    return dataOut;
        //}


        // WIP
        public Image ApplyMask(float[][] mask)
        {
            int size = mask.GetLength(0);
            Image outImg = new Image(new Bitmap(bmp.Width, bmp.Height, PixelFormat.Format32bppArgb));

            // Get the address of the first line.
            IntPtr srcPtr = GetDataPointer();
            IntPtr dstPtr = outImg.GetDataPointer();
            unsafe
            {
                uint* pBase = (uint*)srcPtr.ToPointer();
                uint* pDest = (uint*)dstPtr.ToPointer();
                
                int width = bmpData.Stride / 4;
                int radius = size >> 1;

                int yDone = bmpData.Height - radius;
                int xDone = bmpData.Width - radius;

                for (int y = radius; y < yDone; y++)
                {
                    for (int x = radius; x < xDone; x++)
                    {
                        float sumR = 0, sumG = 0, sumB = 0;
                        for (int mY = -radius; mY <= radius; mY++)
                        {
                            for (int xY = -radius; xY <= radius; xY++)
                            {
                                byte* bgra = (byte*)&pBase[(y + mY) * width + x + xY];
                                float maskValue = mask[mY + radius][xY + radius];
                                sumB += bgra[0] * maskValue;
                                sumG += bgra[1] * maskValue;
                                sumR += bgra[2] * maskValue;
                            }
                        }

                        pDest[y * width + x] = (uint)(0xFF << 24 | (byte)sumR << 16 | (byte)sumG << 8 | (byte)sumB);
                    }
                }
            }

            Unlock();
            outImg.Unlock();

            return outImg;
        }

        public ImageData GetImageData()
        {
            ImageData data = new ImageData(Height, Width);
            IntPtr dataPtr = GetDataPointer();

            int numPixels = Height * Width;
            int numBytes = numPixels * 4;
            unsafe
            {
                try
                {
                    fixed (void* dest = data.Data)
                    {
                        MoveMemory(dest, dataPtr.ToPointer(), numBytes);
                    }
                }
                catch (EntryPointNotFoundException)
                {
                    int* dst = (int*)Marshal.UnsafeAddrOfPinnedArrayElement(data.Data, 0);
                    int* src = (int*)dataPtr;

                    for (int i = 0; i < numPixels; ++i)
                    {
                        dst[i] = src[i];
                    }
                }
            }


            Unlock();
            return data;
        }

        public Histogram CreateHistogram(int binsize, char channel = 'x')
        {
            IntPtr basePointer = GetDataPointer(new Rectangle(Point.Empty, bmp.Size));
            // Determine offset from pixel pointer.
            // Treating ARGB as a byte array in little-endian causes
            // order to be reversed to BGRA.
            int offset = 0;
            if (channel == 'r')
            {
                offset = 2;
            }
            else if (channel == 'g')
            {
                offset = 1;
            }

            // Determine bytesize for pixelformat.
            int pixelSize = System.Drawing.Image.GetPixelFormatSize(bmpData.PixelFormat) / 8;


            Histogram hist = new Histogram(binsize);
            float factor = 256.0f / binsize;

            unsafe
            {
                byte* pBase= (byte*) basePointer;
                for (int y = 0 ; y < bmpData.Height ; ++y)
                {
                    byte* row = pBase + y * bmpData.Stride;
                    for (int x = 0; x < bmpData.Width; ++x)
                    {
                        byte val = row[x * pixelSize + offset];
                        ++hist[(int)(val / factor)];
                    }
                }
            }

            Unlock();
            return hist;
        }

        public Histogram CreateHSLHistogram(int binSize)
        {
            Histogram hist = new Histogram(binSize);

            IntPtr basePointer = GetDataPointer(new Rectangle(Point.Empty, bmp.Size));
            // Determine bytesize for pixelformat.
            int pixelSize = System.Drawing.Image.GetPixelFormatSize(bmpData.PixelFormat) / 8;

            unsafe
            {
                byte* pBase = (byte*)basePointer;
                for (int y = 0; y < bmpData.Height; ++y)
                {
                    byte* row = pBase + y * bmpData.Stride;
                    for (int x = 0; x < bmpData.Width; ++x)
                    {
                        byte* bgra = &row[x * pixelSize];
                        double lightness = Color.GetLightness(bgra[2], bgra[1], bgra[0]);
                        ++hist[(int)(lightness * (binSize-1))];
                    }
                }
            }

            
            Unlock();
            return hist;
        }

        public HSVValue GetHSVForPixel(int x, int y)
        {
            System.Drawing.Color clr = bmp.GetPixel(x, y);
            return Color.RGBtoHSV(clr.R, clr.G, clr.B);
        }

        // Combines image with this image.
        // This image is changed!!
        public Image Combine(Image image)
        {
            // Get the address of the first line.
            IntPtr dstPtr = GetDataPointer();
            IntPtr srcPtr = image.GetDataPointer();
            unsafe
            {
                uint* pBase = (uint*)srcPtr.ToPointer();
                uint* pDest = (uint*)dstPtr.ToPointer();
                for (int i = 0; i < (bmpData.Stride / 4) * bmpData.Height; i++)
                {
                    byte* argbSrc = (byte*)pBase, argbDst = (byte*)pDest;
                    argbDst[0] = (byte)(argbDst[0] + argbSrc[0] >> 1);
                    argbDst[1] = (byte)(argbDst[1] + argbSrc[1] >> 1);
                    argbDst[2] = (byte)(argbDst[2] + argbSrc[2] >> 1);
                    pBase++;
                    pDest++;
                    //*(pDest++) = processPixel(*(pBase++));
                }
            }

            Unlock();
            image.Unlock();
            
            return this;
        }

        // Added 6-3-2012
        public Image Resize(int width, int height) {
            Image outputImage = new Image(new Bitmap(width, height));

            Graphics g = Graphics.FromImage(outputImage.bmp);
            g.DrawImage(this.bmp, 0, 0, width, height);

            return outputImage;
        }

        public Image Resize(int width, int height, Rectangle rect) {

            

            // Figure out the ratio
            double ratioX = (double)width / (double)rect.Width;
            double ratioY = (double)height / (double)rect.Height;
            double ratio = ratioX < ratioY ? ratioX : ratioY; // use whichever multiplier is smaller

            // now we can get the new height and width
            int newWidth = Convert.ToInt32(rect.Width * ratio);
            int newHeight = Convert.ToInt32(rect.Height * ratio);
            

            // Now calculate the X,Y position of the upper-left corner 
            // (one of these will always be zero)
            int posX = Convert.ToInt32((width - (rect.Width * ratio)) / 2);
            int posY = Convert.ToInt32((height - (rect.Height * ratio)) / 2);


            Image outputImage = new Image(new Bitmap(width, height));
            Graphics g = Graphics.FromImage(outputImage.bmp);

            g.InterpolationMode = InterpolationMode.Low;
            g.SmoothingMode = SmoothingMode.None;
            g.PixelOffsetMode = PixelOffsetMode.None;
            g.CompositingQuality = CompositingQuality.GammaCorrected;

            g.Clear(System.Drawing.Color.White); // white padding
            g.DrawImage(bmp, posX, posY, newWidth, newHeight);

            g.Dispose();

            return outputImage;

        }

        public Image Crop(Rectangle rect) {
            rect.Width++;
            rect.Height++;
            Image outputImage = new Image(new Bitmap(rect.Width, rect.Height));
           
            Graphics g = Graphics.FromImage(outputImage.bmp);
            g.DrawImage(this.bmp, new Rectangle(0, 0, rect.Width, rect.Height), rect, GraphicsUnit.Pixel);

            rect.Width--;
            rect.Height--;

            return outputImage;
        }

        public int TestBitmaps(Image rhs) {
            int sum = 0;

             //Get the address of the first line.
            IntPtr lhsPtr = GetDataPointer();
            IntPtr rhsPtr = rhs.GetDataPointer();
            unsafe {
                uint* pLHS = (uint*)lhsPtr.ToPointer();
                uint* pRHS = (uint*)rhsPtr.ToPointer();
                for (int i = 0; i < (bmpData.Stride / 4) * bmpData.Height; i++) {
                    if (*pLHS++ == *pRHS++)
                    {
                        ++sum;
                    }
                }
            }

            Unlock();
            rhs.Unlock();

            return sum;
        }

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
        private static unsafe extern void MoveMemory(void* dest, void* src, int size);
    }
}
